package cn.jervain.nut.auth.core.provider;

import cn.jervain.nut.auth.core.authentication.Authentication;
import cn.jervain.nut.auth.core.encoder.PasswordEncoder;
import cn.jervain.nut.auth.core.exception.AuthException;
import cn.jervain.nut.auth.core.storage.TokenStorage;
import cn.jervain.nut.auth.core.token.Token;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("ALL")
public abstract class AuthProvider<T> implements InitializingBean {

    private String usernameParam = "username";
    private String passwordParam = "password";

    private String authEndpoint = "/login";
    private HttpMethod authMethod = HttpMethod.POST;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private TokenStorage<T> tokenStorage;
    @Autowired
    private ObjectMapper objectMapper;

    protected AuthProvider() {}

    protected AuthProvider(
            String usernameParam,
            String passwordParam,
            String authEndpoint,
            HttpMethod authMethod
    ) {
        this.usernameParam = usernameParam;
        this.passwordParam = passwordParam;
        this.authEndpoint = authEndpoint;
        this.authMethod = authMethod;
    }


    public boolean isLogin(HttpServletRequest request) {
        return authMethod.matches(request.getMethod()) &&
                antPathMatcher.match(authEndpoint, request.getRequestURI());
    }

    public void auth(HttpServletRequest request, HttpServletResponse response) {
        try {
            String username = getUsernameParam(request);
            String rawPassword = getPasswordParam(request);
            Authentication<T> authentication = retrieveUser(username);
            if (!passwordEncoder.matches(rawPassword, authentication.getCredentials())) {
                throw new AuthException(401, "用户名或者密码错误");
            }
            additionalCheck(authentication);
            onSuccess(authentication, request, response);
        } catch (AuthException e) {
            onFailure(e, request, response);
        }
    }

    protected void onSuccess(Authentication<T> authentication, HttpServletRequest request, HttpServletResponse response) {
        Token<T> token = authentication.transform();
        String sign = tokenStorage.put(token);

        Map<String, Object> map = new HashMap<>();
        map.put("token", sign);
        map.put("detail", authentication);

        try {
            response.addHeader("Content-Type", "application/json");
            objectMapper.writeValue(response.getOutputStream(), map);
            response.flushBuffer();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    protected void onFailure(AuthException e, HttpServletRequest request, HttpServletResponse response) {
        e.printStackTrace();
    }

    protected void additionalCheck(Authentication authentication) throws AuthException {
        if (authentication.isLocked()) {
            throw new AuthException(401, "账户已被锁定");
        }
    }

    protected String getUsernameParam(HttpServletRequest request) {
        return request.getParameter(usernameParam);
    }

    protected String getPasswordParam(HttpServletRequest request) {
        return request.getParameter(passwordParam);
    }

    protected abstract Authentication<T> retrieveUser(String username);

    @Override
    public void afterPropertiesSet() throws Exception {
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
    }

}
